package com.lpb.spring.lpbspring.getway.util;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

/**
 * java反射相关帮助类
 */
public abstract class ClassUtils {
    /**
     * 存放类与属性关系
     */
    private final static Map<Class<?>, Map<String, Field>> fieldsMap = new HashMap<>();

	public static Collection<Field> getDeclaredFields(Class<?> modelClass) {
		return getDeclaredFieldsMap(modelClass).values();
	}

	public static Set<String> getDeclaredFieldNames(Class<?> modelClass) {
        return getDeclaredFieldsMap(modelClass).keySet();
    }

	public static Map<String, Field> getDeclaredFieldsMap(Class<?> modelClass) {
		if (!fieldsMap.containsKey(modelClass)) {// 如果不存在 将对象属性加入到集合中
			return getDeclaredFields(modelClass, true);
		} else {
			return fieldsMap.get(modelClass);
		}
	}

	/**
	 * @param thisClass
	 * @param isIgnore
	 *            是否忽略静态字段及存储字段
	 * @return
	 */
    public static Map<String, Field> getDeclaredFields(Class<?> thisClass,
                                                        boolean isIgnore) {
		Map<String, Field> fieldsMap = new HashMap<>();
		for (Field field : thisClass.getDeclaredFields()) {
			if (isIgnore && (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()))) {
				continue;
			}
			if(!fieldsMap.containsKey(field.getName())) {
			    fieldsMap.put(field.getName(), field);
            }
		}
		Class<?> supperClass = thisClass.getSuperclass();
		if (supperClass != null) {
			fieldsMap.putAll(getDeclaredFields(supperClass, isIgnore));
		}
		return fieldsMap;
	}

	public static void set(Object obj, Field field, Object value) {
        set(obj, field.getName(), value);
    }

	public static void set(Object obj, String fieldName, Object fieldValue){
		Class<?> clazz = obj.getClass();
		try {
            Method setMethod;
            try {
                setMethod = clazz.getDeclaredMethod("set" + StringUtils.capitalize(fieldName), fieldValue.getClass());
                setMethod.invoke(obj, fieldValue);
            } catch (Exception e) {
                Map<String, Field> fieldMap = getDeclaredFieldsMap(clazz);
                Field field = fieldMap.get(fieldName);
                if(field != null) {
                    field.setAccessible(true);
                    field.set(obj, fieldValue);
                }
			}
		} catch (Exception ignored) {

		}
	}

	public static <T> T get(Object obj, String fieldName, Class<T> type){
        Object o = get(obj, fieldName);
        return o != null ? type.cast(o) : null;
    }

    public static <T> T get(Object obj, String fieldName, Class<T> type, T defaultValue){
        Object o = get(obj, fieldName);
        return o != null ? type.cast(o) : defaultValue;
    }

    public static Object get(Object obj, Field field) {
        return get(obj, field.getName());
    }


    public static Object get(Object obj, String fieldName){
		Class<?> clazz = obj.getClass();
		Object value = null;
		try {
            Method getMethod;
            try {
                try{
                    getMethod = clazz.getDeclaredMethod("get" + StringUtils.capitalize(fieldName));
                } catch (NoSuchMethodException e) {
                    getMethod = clazz.getDeclaredMethod("get" + fieldName);
                }
                value = getMethod.invoke(obj);
            } catch (NoSuchMethodException e) {
                Map<String, Field> fieldMap = getDeclaredFieldsMap(clazz);
                Field field = fieldMap.get(fieldName);
                if(field != null){
                    field.setAccessible(true);
                    value = field.get(obj);
                    field.setAccessible(false);
                }
            }
		} catch (Exception ignored) {
		}
		return value;
	}

    public static List<Class<?>> getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread()
                .getContextClassLoader();
        String path = packageName.replace(".", "/");
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile()));
        }
        ArrayList<Class<?>> classes = new ArrayList<>();
        for (File directory : dirs) {
            directory = new File(URLDecoder.decode(directory.getPath(), "UTF-8"));
            classes.addAll(findClass(directory, packageName));
        }
        return classes;
    }

    public static List<Parameter> getParameters(Method method) {
        return Arrays.asList(method.getParameters());
    }

    public static Object invoke(Object service, String methodName, Object[] args) {
        Class<?>[] argClasses = new Class<?>[args.length];
        for (int i = 0; i < args.length; i++) {
            argClasses[i] = args[i].getClass();
        }
        try {
            Method method = service.getClass()
                    .getDeclaredMethod(methodName, argClasses);
            method.setAccessible(true);
            return method.invoke(service, args);
        } catch (NoSuchMethodException | SecurityException ignored) {

        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean isNull(Object obj, String fieldName) {
        return get(obj, fieldName) == null;
    }

    public static List<Class<?>> findClass(File directory, String packageName)
            throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        assert files != null;
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClass(file,
                        packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName
                        + "."
                        + file.getName().substring(0,
                        file.getName().length() - 6)));
            }
        }
        return classes;
    }

    private static Map<Class<?>, Map<Class<? extends Annotation>, Boolean>> classAnnotationMap = new HashMap<>();

    private static boolean beAnnotationPresent(Class<?> clazz, Class<? extends Annotation> a) {
        for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
            if (c.isAnnotationPresent(a)) {
                return true;
            }
            if(isAnnotationPresentOnInterfaces(c, a)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> a) {
        Map<Class<? extends Annotation>, Boolean> classBooleanMap = classAnnotationMap.get(clazz);
        Boolean aBoolean;
        if(classBooleanMap != null) {
            aBoolean = classBooleanMap.get(a);
            if(aBoolean == null) {
                aBoolean = beAnnotationPresent(clazz, a);
                classBooleanMap.put(a, aBoolean);
            }
            return aBoolean;
        } else {
            HashMap<Class<? extends Annotation>, Boolean> classBooleanHashMap = new HashMap<>();
            aBoolean = beAnnotationPresent(clazz, a);
            classBooleanHashMap.put(a, aBoolean);
            classAnnotationMap.put(clazz, classBooleanHashMap);
        }
        return aBoolean;
    }

    private static boolean isAnnotationPresentOnInterfaces(Class<?> clazz, Class<? extends Annotation> a) {
        for(Class<?> i : clazz.getInterfaces()) {
            if( i.isAnnotationPresent(a) ) {
                return true;
            }
            if(isAnnotationPresentOnInterfaces(i, a)) {
                return true;
            }
        }

        return false;
    }

    public static void modifyAnnotationField(Annotation annotation, String key, Object val) throws NoSuchFieldException, IllegalAccessException {
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
        Field value = invocationHandler.getClass().getDeclaredField("memberValues");
        value.setAccessible(true);
        Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
        memberValues.put(key, val);
    }

    public static void modifyStaticFinalField(Class<?> clazz, String fieldName, Object val) throws IllegalAccessException, NoSuchFieldException {
        Field field = clazz.getField(fieldName);
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, val);
    }

    public static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotationType){
        T annotation = clazz.getAnnotation(annotationType);
        if (Objects.isNull(annotation) && clazz.getSuperclass() != null) {
            return getAnnotation(clazz.getSuperclass(), annotationType);
        }
        return annotation;
    }

    public static StackTraceElement getSelfStackTrace() {
        return Thread.currentThread().getStackTrace()[2];
    }

    public static <T> T newInstance (Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean isSuper(Class<?> child, Class<?> parent) {

        Class<?> superclass = child.getSuperclass();

        boolean isSuper = parent == superclass;
        List<Class<?>> interfaces = new ArrayList<>(Arrays.asList(child.getInterfaces()));
        while (!isSuper && Objects.nonNull(superclass)) {
            interfaces.addAll(Arrays.asList(superclass.getInterfaces()));
            superclass = superclass.getSuperclass();
            isSuper = superclass == parent;
        }
        if (!isSuper) {
            for (Class<?> inter : interfaces) {
                if (inter == parent) {
                    isSuper = true;
                    break;
                }
            }
        }
        return isSuper;
    }
}
